Safari Script Loading on macOS
Safari Browser Scripts let Scripting run userscripts in Safari through the Safari Web Extension. The runtime supports Greasemonkey-style GM.* APIs and selected Scripting APIs.
Where Scripts Run
There are two sources:
browser.tsxin a Scripting script project. It is built tobrowser.js.- Installed
.user.js/.jsfiles from Safari's extension popup.
Scripts installed from Safari's extension popup are stored under:
Downloads and GM storage live in the same root:
This root follows the Safari Browser Data storage location configured in Settings, including WebDAV when enabled.
Script Format
Create or install a .user.js / .js file with a userscript metadata block:
Common metadata keys:
@weight controls execution order when multiple scripts match the page. Larger weights run first. If no @run-at is provided, scripts run at document-end.
@inject-into defaults to auto. With auto, a script that declares any @grant (GM.* or Scripting.*) runs in the content world (privileged APIs available, isolated from the page's JavaScript), while a script with @grant none or no grant runs in the page world (shares the page's real window, can read page globals). Use @inject-into content or @inject-into page to force a world explicitly. GM.*, Scripting.*, and extension messaging are only available in the content world; grants are ignored in the page world. On pages whose Content-Security-Policy blocks injected scripts, auto page-world scripts automatically fall back to the content world.
Permissions
Privileged APIs must be declared with @grant.
@grant none disables GM APIs, but GM_info and GM.info remain available for compatibility.
Cross-origin network, download, resource, and cookie access must be allowed with @connect.
When a required grant or connect rule is missing, the runtime throws an error with a stable code such as permissionDenied or connectDenied.
GM_info
GM_info and GM.info expose parsed metadata and runtime details:
Supported GM APIs
Storage
GM storage is stored as JSON under scripting-safari-extension/storages/.
DOM and Menu
Menu commands are shown in Safari's extension popup for the current page.
Tabs, Clipboard, Notification
GM.closeTab() closes the current tab when supported by Safari. GM.openInTab() returns a tab control with close().
Resources
Declare resources in the metadata block:
Download
Downloaded files are stored under scripting-safari-extension/downloads/.
XHR
The runtime supports responseType values text, json, arraybuffer, blob, and document, plus user, password, headers, data, timeout, binary, overrideMimeType, upload callbacks, finalUrl, and responseURL.
Cookies
By default, GM.cookie.list, set, and delete use the Cookie store of the tab running the script, including its Safari profile or private-browsing session. An explicit storeId overrides this choice. If Safari cannot uniquely identify the originating tab's store, the operation reports an error rather than accessing the background's default store. Browser site permissions and @connect still apply. Cookie visibility also depends on domain, path, Secure and HttpOnly attributes; HttpOnly cookies are not visible through document.cookie.
Callback style is also supported:
Scripting.FileManager
Use @grant Scripting.FileManager or @grant Scripting.* to access the API.
Properties
In regular Scripting app scripts, the same Safari data directories are available as FileManager.safariBrowserDirectory, FileManager.safariBrowserStorageDirectory, FileManager.safariBrowserDownloadsDirectory, and FileManager.safariBrowserUserscriptsDirectory.
Methods
All file operations are limited to directories exposed by Scripting.FileManager.
Scripting.tabs
Use @grant Scripting.tabs (or @grant Scripting.*) to enumerate and switch the real open Safari tabs.
This is different from GM.getTabs, which is a per-script storage bus that only returns data your own script saved with GM.saveTab — it does not list real tabs or their URLs. Scripting.tabs returns the actual open tabs.
Methods
Each tab is a ScriptingTabInfo:
To open a new tab use GM.openInTab(url); to close a tab use GM.closeTab(id) with an id from query(). Scripting.tabs.activate() only switches focus and never opens a new tab. On a single Safari window, activate() selects the tab; across separate windows it selects the tab within its own window.
Privacy: A script granted
Scripting.tabscan read the URL and title of every open tab, not just the page it runs in. Only grant it to scripts you trust. Userscripts require the Scripting extension to be allowed on the page, and must declare this grant explicitly.
Installed Scripts
Safari's extension popup can install userscripts from the current page or from a URL. Installed scripts can be enabled, disabled, updated, or deleted in the popup and in More > Development > Safari Browser Scripts.
Use the More page to inspect:
- Installed userscripts.
- GM storage JSON files.
- Downloads created by
GM.download.
Example
No-UI translation (iOS/macOS 26+)
Declare // @grant Scripting.Translation and use Scripting.Translation.translateInstalled({text, source, target?}) or translateInstalledBatch({texts, source, target?}) in the content world. Requires installed languages; source is mandatory. Safari exposes only these two new no-UI methods, without download prompts or a translation host. A request accepts up to 64 texts and 256 KiB of UTF-8 text. See the Translation documentation.
Automatic installation detection
Script loading supports storage directory aliases. Files must remain within an allowed script-storage directory after symbolic links are resolved; links to files outside those directories are rejected. A permissionDenied response indicates a rejected path, while assetFetchFailed indicates that an allowed script file could not be read.
A .js link can point to a regular file with a different extension inside an allowed directory. Loading pins the opened file and rejects replacement symbolic links and non-regular files, including named pipes.
The current main document must be raw script text with a complete, correctly ordered userscript comment header at the beginning and a nonempty @name (including localized names). A BOM, whitespace and leading license comments are allowed. Embedded code in configurations, quoted examples and HTML code examples do not trigger installation. URLs ending in .conf, .ini, .yaml, .yml, .json, .toml or .mobileconfig are not redirected automatically.
The extension confirms the source document before redirecting; leaving or reloading it cancels older detection requests. Automatic redirection can be disabled, and going back from the installation page does not immediately redirect again. Manual installation by URL still supports raw script endpoints, but the fetched content must also have a valid leading metadata block.
Returning to the source or clicking the source link on the installation page keeps the script readable in the same tab, including after the extension background reloads. This protection ends when the tab navigates away from the source and its installation page, or closes. Done/Close closes the tab.
